/*******************************************************************************
this code is protected by the GNU affero GPLv3
author:Sylvain BERTRAND <sylvain.bertrand AT gmail dot com>
*******************************************************************************/
#include <ulinux/compiler_types.h>
#include <ulinux/sysc.h>
#include <ulinux/types.h>
#include <ulinux/error.h>
#include <ulinux/socket/socket.h>
#include <ulinux/socket/msg.h>
#include <ulinux/socket/netlink.h>
#include <ulinux/epoll.h>
#include <ulinux/utils/mem.h>

#include "out.h"
#include "ulinux_namespace.h"
#include "globals.h"
#include "uevent.h"
static i ep_fd;
static i so;

void uevents_cleanup(void)
{
  l r;
  loop{
    r=close(ep_fd);
    if(r!=-EINTR) break;
  }
  if(ISERR(r)){
    OUT("ERROR(%ld):unable to close epoll fd\n",r);
    exit_group(-1);
  }

  loop{
    r=close(so);
    if(r!=-EINTR) break;
  }
  if(ISERR(r)){
    OUT("ERROR(%ld):unable to close netlink socket\n",r);
    exit_group(-1);
  }
}

void uevents_setup(void)
{
  i recv_buf_sz;
  i r;
  struct sockaddr_nl addr={AF_NETLINK,0,0,1};
  struct epoll_event ep_evt;

  OUT(PRE "setting up uevent...");
  ep_fd=(i)epoll_create1(0);
  if(ISERR(ep_fd)){
    OUT("ERROR(%d):unable to create epoll fd\n",ep_fd);
    exit_group(-1);
  }

  /*--------------------------------------------------------------------------*/

  /* blocking socket */
  so=(i)socket(PF_NETLINK,SOCK_RAW,NETLINK_KOBJECT_UEVENT);
  if(ISERR(so)){
    OUT("ERROR(%d):unable to create uevent netlink socket\n",so);
    exit_group(-1);
  }

  /*--------------------------------------------------------------------------*/

  recv_buf_sz=128*1024;/* 128k for kernel buffering */
  r=setsockopt(so,SOL_SOCKET,SO_RCVBUFFORCE,&recv_buf_sz,sizeof(recv_buf_sz));
  if(ISERR(r)){
    OUT("ERROR(%ld):unable to force the size of the socket buffer\n",r);
    exit_group(-1);
  }

  /*--------------------------------------------------------------------------*/

  /* uevent groups-->only one: 1 */
  r=bind(so,&addr,sizeof(addr));
  if(ISERR(r)){
    OUT("ERROR(%ld):unable to bind address to uevent netlink socket\n",r);
    exit_group(-1);
  }
  
  /*--------------------------------------------------------------------------*/

  memset(&ep_evt,0,sizeof(ep_evt));
  ep_evt.events=EPOLLIN;
  ep_evt.data.fd=so;
  r=epoll_ctl(ep_fd,EPOLL_CTL_ADD,so,&ep_evt);
  if(ISERR(r)){
    OUT("ERROR(%ld):unable to register uevent netlink socket to epoll\n",r);
    exit_group(-1);
  }
  OUT("done\n");
}

static u8 uevent_msg(void)
{
  u8 buf[8192];/*presume 8kB is enough for one message*/
  struct io_vec uev_io_vec;
  struct msg_hdr msg;
  l r;

  memset(buf,0,sizeof(buf));

  uev_io_vec.base=buf;
  uev_io_vec.len=sizeof(buf);

  memset(&msg,0,sizeof(msg));
  msg.iov=&uev_io_vec;
  msg.iov_len=1;

  loop{
    r=recvmsg(so,&msg,0);
    if(r!=-EINTR) break;
  }
  if(ISERR(r)){
    OUT("ERROR(%ld):unable to receive the uevent\n",r);
    exit_group(-1);
  }
  if(msg.flgs&MSG_TRUNC){
    OUT("ERROR:the uevent was truncated(flags=0x%x)\n",msg.flgs);
    exit_group(-1);
  }
  return uevent_process(&buf[0],(i)r);
}

u8 uevents_process(void)
{
  u8 r;

  r=ROOT_NOT_FOUND;
  OUT(PRE "processing uevents...\n");
  loop{
    l r1;
    static struct epoll_event evts;/*uevent netlink event*/
    loop{
      memset(&evts,0,sizeof(evts));
      r1=epoll_wait(ep_fd,&evts,1,UEVENTS_TIMEOUT);

      if(r1!=-EINTR) break;
    }
    if(ISERR(r1)){
      OUT(PRE "ERROR(%ld):error epolling uevent netlink socket\n",r1);
      exit_group(-1);
    }
    if(!r1) break;/*assume no more uevents and unable to find root*/
    if(evts.events&EPOLLIN){
      r=uevent_msg();
      if(r==ROOT_FOUND) break;
    }else{
      OUT(PRE "ERROR:unmanaged epolling event on uevent netlink socket(events=%u)\n",
                                                                   evts.events);
      exit_group(-1);
    }
  }
  OUT(PRE "uevents processing terminated\n");
  return r;
}
